#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <regex.h>
#include <string.h>
#include <errno.h>
#include <fstream>
#include <json/json.h>
#include "zk_client.h"
#include <iostream>
#include <sys/wait.h>
#include <string.h>

#define CONF_OK 0
#define CONF_OPEN_FAIL -1
#define CONF_FORMAT_ERR -2
#define CONF_ZK_CONNECT_ERR -3
#define CONF_ZK_READ_ERR -4

#define INFO_HEADER "[INFO] "
#define ERR_HEADER "[ERR] "
#define WARN_HEADER "[WARN] "

#define JR_VERSION "1.0"

using namespace std;


const char *err_str(int code)
{
	switch (code) {
		case CONF_OK:
			return "OK";
		case CONF_OPEN_FAIL:
			return "open config file fail";
		case CONF_FORMAT_ERR:
			return "config file format error";
		case CONF_ZK_CONNECT_ERR:
			return "connect zookeeper server fail";
		case CONF_ZK_READ_ERR:
			return "read from zookeeper server fail";
		default:
			return "unknown error";
	}
}

typedef map<string, string> VariableMap;
static VariableMap global_variable;

static int load_global_variable_from_file(const char* path, 
				VariableMap& m) {
	ifstream ifs;
	ifs.open(path);
	if (!ifs.is_open()) {
		cout << "load_global_variable_from_file fail, fail to open:" 
				<< path << endl;
		return -1;
	}
				 
	Json::Reader reader;
	Json::Value root;
	if (!reader.parse(ifs, root, false)) {
		cout << "load_global_variable_from_file fail, fail to parse file:" 
				<< path << endl;
		return -1;
	}
	
	Json::Value::iterator it = root.begin();
	for (; it != root.end(); ++it) {
		if (!it->isString()) {
			cout << "load_global_variable_from_file fail, variable_name:" 
					<< it.name() << ", value type is not string" << endl;
			return -1;
		}
		cout << "load_global_variable_from_file, variable_name:" 
				<< it.name() << ", value:" << *it <<endl;
		m[it.name()] = it->asString();
	}
	
	return 0;
}

static int load_global_variable_from_zookeeper(const char* zkpath, 
				VariableMap& m) {
	ZKClient zkc;
	int len = strlen(zkpath);
	int i = 0;
	string zkurl;
	string path = "";
	for (; i < len; ++i) {
		if (zkpath[i] == '/') {
			break;
		}
	}

	//set url
	zkurl.append(zkpath, i);
	//set path
	path.append(zkpath + i, len - i);

	if (!path.size()) {
		path = "/";
	}

	zoo_set_debug_level(ZOO_LOG_LEVEL_ERROR);
	cout << INFO_HEADER << "start get global variable zookeeper:" 
			<< zkurl  << ", path:" << path << endl;
	if (zkc.init(zkurl) != 0) {
		cout << ERR_HEADER << err_str(CONF_ZK_CONNECT_ERR) << endl;
		return -1;
	}
	
	//get global variable from path
	s_vector us;
	int ret = zkc.getChildren(path, us);
	if (ret < 0) {
		cout << ERR_HEADER << err_str(CONF_ZK_READ_ERR) 
				<< ",fail to getChildren from path:" << path << endl;
		return -1;
	}
	
	for (size_t j = 0; j < us.size(); ++j) {
		string data;
		ret = zkc.getNodeData(path + "/" + us[j], data);
		if (ret < 0) {
			cout << ERR_HEADER << err_str(CONF_ZK_READ_ERR) 
					<< ", fail to getNodeData from path:" 
					<< path + "/" + us[j] << endl;
			return -1;
		}
		m[(us[j])] = data;
		cout << INFO_HEADER << (us[j]) << "=" << data << endl; 
		
	}

	return ret;
}

static int process_file(regex_t* reg, const string& infile, 
		const string& outfile, const VariableMap& m);

int init_reg(regex_t* reg, const char* pattern) {
	int ret = regcomp(reg, pattern, REG_EXTENDED);
	
	if (ret) {
		ret = -1;
	}

	return ret;
}

bool IsDir(string path)  
{  
	struct stat sb;  
	if (stat(path.c_str(), &sb) == -1) 
		return false;  
	return S_ISDIR(sb.st_mode);  
} 

int process_path(regex_t* reg, const string& inPath, 
		const string& outPath,
		const VariableMap& m,
		const char* tplext,
		const char* cnfext) {
	
	cout << "process_path, inPath:" << inPath 
			<< ", outPath:" << outPath << endl;
	int ret = 0;
	DIR *dir;
	struct dirent *ptr;
	if ((dir=opendir(inPath.data())) == NULL) {
		ret = -1;
		return ret;
	}
	
	while ((ptr=readdir(dir)) != NULL)
	{
		//cout << "path:" << ptr->d_name << ", d_type:" << (int)ptr->d_type << endl;
		if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0) {	
			//current dir OR parrent dir
			continue;
#if 0
		//in my server d_type is always 0
		} else if(ptr->d_type == DT_DIR) { 
#endif
		} else if(IsDir(inPath + ptr->d_name)) { 
			//dir
			string outDir = outPath + ptr->d_name;
			if (access(outDir.data(), 0) != 0) {
				ret = mkdir(outDir.data(), 0755);
				if (ret < 0) {
					cout << "mkdir fail, path:" << outDir.data() << endl;
					goto exit;
				}
			}
			//cout << "process_path, input:" << (inPath + ptr->d_name)
			//		<< ", output:" << (outPath + ptr->d_name) << endl;
			process_path(reg, inPath + ptr->d_name + "/", outPath + ptr->d_name + "/", 
							m, tplext, cnfext);
		} else {
			string fullname = ptr->d_name;
			size_t p = fullname.rfind(tplext);
			if (p != string::npos && p + strlen(tplext) == fullname.size()) { 
				string fname = fullname.substr(0, p);
				ret = process_file(reg, inPath + "/" + ptr->d_name, 
								outPath + "/" + fname + cnfext, m);
				if (ret < 0)
					break;
			}
		}
	}
exit:
	closedir(dir);
	return ret;
}

static int process_file(regex_t* reg, const string& infile, 
		const string& outfile, const VariableMap& m) {
	cout << "process_file, input:" << infile << ", output:" 
			<< outfile << endl;

	ifstream in(infile.data());
	if (!in.is_open()) {
		return -1;
	}

	ofstream out(outfile.data(), ios::trunc);
	if (!out.is_open()) {
		return -1;
	}

	regmatch_t pm[64];
	const size_t nmatch = 64;
	string l;
	while (getline(in, l)) {
		int ret = 0;
		size_t cnt = 0;
		memset(pm, 0, sizeof(pm));
		for (;cnt < nmatch; ++cnt) {
			pm[cnt].rm_so = -1;
			pm[cnt].rm_eo = -1;
			if (cnt > 0) {
				ret = regexec(reg, l.data() + pm[cnt -1].rm_eo, 
								64 -cnt, pm + cnt, 0);
				//cout << "regexec:" <<  (l.data() + pm[cnt -1].rm_eo) 
				//		<< ", cnt:" << cnt << ", pm[cnt - 1].rm_eo:" 
				//		<< pm[cnt - 1].rm_eo << ", pm[cnt].rm_eo:" 
				//		<< pm[cnt].rm_eo << std::endl;
				if (ret == REG_NOMATCH || pm[cnt].rm_eo <= 0) {
					break;
				}
				pm[cnt].rm_so += pm[cnt -1].rm_eo;
				pm[cnt].rm_eo += pm[cnt -1].rm_eo;
			} else {
				ret = regexec(reg, l.data(), 64, pm, 0);
				//cout << "regexec:" << l.data() << ", cnt:" << cnt 
				//		<< ", pm[cnt].rm_eo:" 
				//		<< pm[cnt].rm_eo << ", pm[cnt].rm_eo:" 
				//		<< pm[cnt].rm_eo << std::endl;
				if (ret == REG_NOMATCH || pm[cnt].rm_eo <= 0) {
					break;
				}
			}
		}

		if (cnt == 0) {
			out << l << "\n";
			continue;
		}

		string newl;
		int lastpos = 0;
		int curpos = 0;
		//cout << "find from:" << l << ", nmatch:" << nmatch << std::endl;
		for (size_t i = 0; i < nmatch && pm[i].rm_so != -1; ++i) {
			//cout << "find from:" << l << ", nmatch:" << nmatch  
			//<< ",i:" << i << std::endl;
			curpos = pm[i].rm_so;
			newl += l.substr(lastpos, curpos - lastpos);

			string variable_name = l.substr(pm[i].rm_so + 2, 
						pm[i].rm_eo - 1 - pm[i].rm_so - 2);
			VariableMap::const_iterator it = m.find(variable_name);
			string variable_value;
			if (it != m.end()) {
				variable_value = it->second;
			} else {
				cout << "can not find the variable:" 
						<< variable_name << endl;
				//cout err message
				return -1;
			}
			
			newl += variable_value;
			lastpos = pm[i].rm_eo;
		}
		


		newl += l.substr(lastpos, string::npos); 

		out << newl << "\n";
	}

	return 0;

}

#define USAGE "Usage: jr [OPTION] [arg] [OPTION] [arg] ...\n\
  -h\t print this text\n\
  -z\t global variable zookeeper path\n\
  -f\t global variable file path\n\
  -v\t print version information\n\
  -i\t template directory, default is ./\n\
  -o\t output directory, default is ./\n\
  -t\t template file name extension, default is .tpl\n\
  -c\t output config file name extension, default is .conf\n"
  //-e\t program to execute\n"

int main(int argc, char **argv)
{
	int ch;
	int ret = 0;
	const char *cfg_file = NULL;
	const char *zkurl = NULL;
	const char *output_dir = "./";
	const char *input_dir = "./";
//	int jr_argc = argc;
//	int prog_argc = 0;
	const char *pattern = "\\$\\{.?\\}";
//	const char *prog = NULL;
	const char *tplextern = ".tpl";
	const char *cnfextern = ".conf";
	
	if (argc < 2) {
		printf(USAGE);
		return 0;
	}
	
	while ((ch = getopt(argc,argv,"t:c:f:i:o:z:e:vh"))!=-1)
	{
		switch(ch)
		{
		case 'f':
			cfg_file = optarg;
			break;
		case 'i':
			input_dir = optarg;
			break;
		case 'o':
			output_dir = optarg;
			break;
		case 'z':
			zkurl = optarg;
			break;
//		case 'e':
//			prog = optarg;
//			break;
		case 'v':
			printf("jr %s\n", JR_VERSION);
			return 0;
		case 'h':
			printf(USAGE);
			return 0;
		case 't':
			tplextern = optarg;
			break;
		case 'c':
			cnfextern = optarg;
			break;
		default:
			printf(USAGE);
			return 0;
		}
	}

	if (!zkurl && !cfg_file) {
		cout << INFO_HEADER 
				<< "you must at least set one of -f and -z at the argvs" 
				<< endl;
		return 0;
	}
	
	if (zkurl != NULL) {
		zoo_set_debug_level(ZOO_LOG_LEVEL_ERROR);
		ret = load_global_variable_from_zookeeper(zkurl, global_variable);
		if (ret < 0) {
			return ret;
		}
	} else if (cfg_file) {
		ret = load_global_variable_from_file(cfg_file, global_variable);
		if (ret < 0) {
			return ret;
		}
	}

	if (input_dir != NULL && access(input_dir, 0) != 0) {
		cout << ERR_HEADER << "can not access directory \"" << input_dir 
				<< "\"" << endl;
		return -1;
	}
	
	if (output_dir != NULL && access(output_dir, 0) != 0) {
		cout << ERR_HEADER << "can not access directory \"" << output_dir 
				<< "\"" << endl;
		return -1;
	}

	regex_t reg;

	ret = init_reg(&reg, pattern);
	if (ret < 0) {
		cout << ERR_HEADER << "init_reg fail" << endl;
		return -1;
	}
		
	ret = process_path(&reg, input_dir, output_dir, global_variable, 
					tplextern, cnfextern);
	if (ret < 0) {
		cout << ERR_HEADER << "process_path fail" << endl;
		return -1;
	}

	cout << "run success!" << endl;

	regfree(&reg);

	return 0;
}
